//需要引入的库：C读取和调用Lua文件的库：lua.h, lauxlib.h, lualib.h
//1.压入元素

void lua_pushnil(lua_State *L);                                        //插入空值
void lua_pushboolean(lua_State *L, int bool);                        //插入布尔值
void lua_pushnumber(lua_State *L, double n);                        //插入double
void lua_pushlstring(lua_State *L, const char* s, size_t length);    //插入任意字符串
void lua_pushstring(lua_State *L, const char* s);                    //插入带'\0'的字符串

//2.查询元素
//1表示第一个被压栈的元素；-1表示栈顶元素；当一个C函数返回后，Lua会清理他的栈，
//所以，有一个原则：永远不要将指向Lua字符串的指针保存到访问他们的外部函数中。
int lua_is*(lua_State *L, int index);        //检查一个元素是否指定类型(number,string,boolean,table)
int lua_toboolean(lua_State *L, int index);
double lua_tonumber(lua_State *L, int index);
const char* lua_tostring(lua_State *L, int index);
size_t lua_strlen(lua_State *L, int index);

//3.stack opt 堆栈操作
int lua_gettop(lua_State *L);                    //返回堆栈中的元素个数
void lua_settop(lua_State *L, int index);        //设置栈顶为一个指定的值，多余值被抛弃，否则压入nil值
void lua_pop(lua_State *L, int n);                //lua_settop(L, -n - 1),从堆栈中弹出n个元素
void lua_pushvalue(lua_State *L, int index);    //压入堆栈上指定一个索引的拷贝到栈顶
void lua_remove(lua_State *L, int index);        //移除指定索引位置的元素
void lua_insert(lua_State *L, int index);        //移动栈顶元素到指定索引的位置
void lua_replace(lua_State *L, int index);        //从栈顶中弹出元素并将其设置到指定索引位置


//4.error 错误处理
//(1)函数
static int panic (lua_State *L);
lua_pcall(L,n,r,f);
//(2)类库
luaL_error(lua_State *L);


//5.调用函数
int  lua_pcall(lua_State *L, int nargs, int nresults, int errfunc); //调用栈顶函数，指定参数格式nargs，返回结果个数，nresults，和错误函数

//6.table opt
void lua_gettable(lua_State *L, int idx);                    //以栈顶元素为key值，获取指定索引的表的值到栈顶
void lua_getfield(lua_State *L, int idx, const char *k);    //获取指定索引的表对应key的值到栈顶
void lua_getglobal(lua_State *L, const char *name);            //等于lua_getfield(L, LUA_GLOBALSINDEX, (name))。获取全局表的变量到栈顶
void lua_settable(lua_State *L, int idx);                    //以栈顶元素为value，栈顶下一元素为key，设置指定索引的表的值
void lua_setfield(lua_State *L, int idx, const char *k);    //弹出栈顶元素，并设置为指定索引的表对应key的值
void lua_setglobal(lua_State *L, const char *name);            //等于lua_setfield(L, LUA_GLOBALSINDEX, (name))。设置全局变量的值
void lua_rawgeti(lua_State *L, int idx, int n);                //获得idx索引的表以n为key的值
void lua_rawget(lua_State *L, int idx);                        //获得idx索引的表以栈顶为key的值
void lua_rawseti(lua_State *L, int idx, int n);                //设置idx索引的表以n为key的值
void lua_rawset(lua_State *L, int idx);                        //设置idx索引的表以栈顶下一个元素为key的值

//7.调用c函数
/*
Lua调用C函数，使用栈进行交互，用来交互的栈不是全局变量，每一个函数都有他自己的私有栈，
第一个参数总是在这个私有栈的index = 1的位置。一个Lua库实际上是一个定义了一系列Lua函数的chunk，
并将这些函数保存在适当的地方，通常作为table 的域来保存
*/
//注册被Lua调用的C函数的格式，以a+b为例
static int func(lua_State *L)
{
    //从栈中获取函数参数并检查合法性，1表示Lua调用时的第一个参数，以此类推
    double arg1 = luaL_checknumber(L, 1);
    double arg2 = luaL_checknumber(L, 2);
    //将函数结果入栈，如果有多个返回值，可以多次入栈
    lua_pushnumber(L, arg1 + arg2);
    //返回值表示该函数的返回值的数量
    return 1;
}

int main()
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    //注册被Lua调用的C函数，参数"add"表示Lua调用时使用的全局函数名，func为被调用的C函数
    lua_register(L, "add", func);
    luaL_dostring("print(add(1, 2))");
    lua_close(L);
    return 0;
}

/////////////C函数库/////////////////////////
//注册被Lua调用的C函数的格式，以a+b为例，函数必须以C的形式被导出
extern "C" int func(lua_State *L)
{
    //从栈中获取函数参数并检查合法性，1表示Lua调用时的第一个参数，以此类推
    double arg1 = luaL_checknumber(L, 1);
    double arg2 = luaL_checknumber(L, 2);
    //将函数结果入栈，如果有多个返回值，可以多次入栈
    lua_pushnumber(L, arg1 + arg2);
    //返回值表示该函数的返回值的数量
    return 1;
}

static luaL_Reg mylibs[] =
{
    {"add", func},
    {NULLL, NULL},
};

//注册mylibs库，Lua中使用该库的用法：testlibs.add(1, 2)
extern "C" __declspec(dllexport)
int luaopen_testlibs(lua_State* L) 
{
    luaL_newlib(L, mylibs);
    return 1;
}

//8.字符串处理
/*
(1)当C函数接受一个来自lua的字符串作为参数时，有两个规则必须遵守：当字符串正在被
访问的时候不要将其出栈；永远不要修改字符串。

(2)当C函数需要创建一个字符串返回给lua的时候，C代码负责缓冲区的分配和释放，
负责处理缓冲溢出等情况。

*/

void lua_concat(lua_State *L, int n);        //连接栈顶开始的n个字符串，弹出这n个字符串并压栈结果
const char *lua_pushfstring(lua_State *L, const char *fmt, ...);    //根据格式串fmt的要求创建一个新的字符串。


//9.注册表
/*使用LUA_REGISTRYINDEX索引来保存注册表中的Lua值。任何C库都可以在这张表里保存数据， 为了防止冲突，
可以使用保护库名前缀的名字作为key值。注册表中的整数key用于应用机制luaL_ref。*/


//10.闭包 upvalue
//一个C函数和它的upvalues的组合称为闭包。upvalues为函数能访问的外部局部变量。

static int counter(lua_State *L)
{
    double val = lua_tonumber(L, lua_upvalveindex(1));
    lua_pushnumber(L, ++val);
    lua_pushvalue(L, -1);
    lua_replace(L, lua_upvalueindex(1));
    return 1;
}

int newCounter(lua_State *L)
{
    lua_pushnumber(L, 0);
    //第二个参数为基本函数，第三个参数是upvalues的个数
    lua_pushcclosure(L, &counter, 1);
    return 1;
}

//11.userdata
void *lua_newuserdata(lua_State *L, size_t size);        //按照指定size的大小分配一段内存放入栈内，并返回这个地址


//12.metatable
//栈顶创建metatable，建立表和registry中类型名的双向关系(分别以tname为表的key和以表为tname的key)
int luaL_newmetatable(lua_State *L, const char* tname);
void luaL_getmetatable(lua_State *L, const char* tname);             //获取registry中tname对应的表
//检查指定索引的元素是否为带有给定tname的metatable的useratum，如果是，返回useratum的地址，否则返回NULL
void* luaL_checkudata(lua_State *L, int index, const char* tname);